using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Linq.Expressions;
using System.Windows;

namespace ReactiveCode.Expressions
{
    using Observables;
    using Observables.Expressions;

    partial class ReactiveExpression
    {
        static ObservableFactory<IObservable<T>> ToObservable<T>(Expression<Func<T>> expression)
        {
            Contract.Requires(expression != null);
            return ExpressionConverter.ToObservable<T>(expression.Body);
        }

        public static ReactiveExpression If(ObservableFactory<IObservable<bool>> test, ReactiveExpression trueExpression, ReactiveExpression falseExpression = null)
        {
            Contract.Requires<ArgumentNullException>(test != null, "test");
            return new IfExpression(test, trueExpression, falseExpression);
        }
        public static ReactiveExpression If(Expression<Func<bool>> test, ReactiveExpression trueExpression, ReactiveExpression falseExpression = null)
        {
            Contract.Requires<ArgumentNullException>(test != null, "test");
            var result = (IfExpression)If(ToObservable(test), trueExpression, falseExpression);
            result.Condition = test.Body.ToString();
            result.Name = "if " + test.Body;
            return result;
        }

        public static ReactiveExpression Assignment<T>(ObservableFactory<ISubject<T>> target, ObservableFactory<IObservable<T>> value)
        {
            Contract.Requires<ArgumentNullException>(target != null, "target");
            Contract.Requires<ArgumentNullException>(value != null, "value");
            return new Assignment<T>(target, value);
        }
        public static ReactiveExpression Assignment<T>(Expression<Func<T>> target, Expression<Func<T>> value)
        {
            Contract.Requires<ArgumentNullException>(target != null, "target");
            Contract.Requires<ArgumentNullException>(value != null, "value");
            var result = (Assignment<T>)Assignment(ExpressionConverter.ToSubject<T>(target.Body), ToObservable(value));
            result.Target = target.Body.ToString();
            result.Value = value.Body.ToString();
            result.Name = string.Format("{0} = {1}", target.Body, value.Body);
            return result;
        }
        public static ReactiveExpression Assignment(Expression<Func<DependencyObject>> dependencyObject, DependencyProperty property, Expression<Func<object>> value)
        {
            Contract.Requires<ArgumentNullException>(dependencyObject != null, "dependencyObject");
            Contract.Requires<ArgumentNullException>(property != null, "property");
            Contract.Requires<ArgumentNullException>(value != null, "value");
            var observableObject = ToObservable(dependencyObject);
            var instanceFunc = dependencyObject.Compile();
            ObservableFactory<ISubject<object>> subject = decorator =>
            {
                var instance = observableObject(decorator);
                var observable = Observable.CreateWithDisposable<object>(o => instance.Subscribe(x => new DependencyPropertyObserverHandle(x, property, o)))
                    .Decorate(decorator);
                var observer = Observer.Create<object>(x => instanceFunc().SetValue(property, x))
                    .Decorate(decorator);
                return Subject.Create<object>(observable, observer);
            };
            var assignment = (Assignment<object>)Assignment(subject, ToObservable(value));
            assignment.Target = dependencyObject.Body + "." + property.Name;
            assignment.Value = value.Body.ToString();
            assignment.Name = assignment.Target + " = " + assignment.Value;
            return assignment;
        }
        public static ReactiveExpression Assignment<T>(Expression<Func<T>> target, T constantValue)
        {
            Contract.Requires<ArgumentNullException>(target != null, "target");
            var result = (Assignment<T>)Assignment(ExpressionConverter.ToSubject<T>(target.Body), _ => Observable.Return(constantValue));
            result.Target = target.Body.ToString();
            result.Value = Convert.ToString(constantValue);
            result.Name = string.Format("{0} = {1}", target.Body, constantValue);
            return result;
        }

        public static ReactiveExpression ForEach<T>(ObservableFactory<IObservableEnumerable<T>> source, Func<IObservable<T>, ReactiveExpression> body)
        {
            Contract.Requires<ArgumentNullException>(source != null, "source");
            Contract.Requires<ArgumentNullException>(body != null, "body");
            return new ForEach<T>(source, body);
        }

        public static ReactiveExpression Block(params ReactiveExpression[] body)
        {
            Contract.Requires<ArgumentNullException>(body != null, "body");
            return Block(body.AsEnumerable());
        }
        public static ReactiveExpression Block(IEnumerable<ReactiveExpression> body)
        {
            Contract.Requires<ArgumentNullException>(body != null, "body");
            var block = new Block();
            foreach (var expr in body)
                block.Body.Add(expr);
            return block;
        }

        public static ReactiveExpression Call<T>(ReactiveMethodDefinition<T> method)
        {
            Contract.Requires<ArgumentNullException>(method != null, "method");
            return new MethodCall<T>(method);
        }

        public static ReactiveExpression Return<T>(ObservableFactory<IObservable<T>> value)
        {
            Contract.Requires<ArgumentNullException>(value != null, "value");
            return new ReturnExpression<T>(value);
        }
        public static ReactiveExpression Return<T>(Expression<Func<T>> value)
        {
            Contract.Requires<ArgumentNullException>(value != null, "value");
            return Return(ToObservable(value));
        }
        public static ReactiveExpression Return()
        {
            return Return<Unit>(_ => Observable.Return(new Unit()));
        }

        public static ReactiveExpression Imperative<T>(ObservableFactory<IObservable<T>> trigger, Action<T> body)
        {
            Contract.Requires<ArgumentNullException>(trigger != null, "trigger");
            Contract.Requires<ArgumentNullException>(body != null, "body");
            return new ImperativeExpression<T>(trigger, body);
        }
        public static ReactiveExpression Imperative<T>(Expression<Func<T>> trigger, Action<T> body)
        {
            Contract.Requires<ArgumentNullException>(trigger != null, "trigger");
            Contract.Requires<ArgumentNullException>(body != null, "body");
            return Imperative(ToObservable(trigger), body);
        }

        public static ReactiveExpression Nop()
        {
            return new ReactiveExpression();
        }
    }
}